#include "stdafx.h"
#include "Scene.h"
#include "..\Render\Render.h"
#include "..\..\Global\globaldef.h"
#include <stdio.h>

std::vector<StreamedMesh*> Scene::g_Geometry = std::vector<StreamedMesh*> ();

/**
	Initalized the scene instance

	@author Dacre Denny
	@return true if succesful otherwise false
*/
bool Scene::Create() {

	return true;
}

/**
	Releases the scene instance and all resource that it internally owns

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool Scene::Release() {

	FOREACH(StreamedMesh*, i, Scene::g_Geometry) {

		StreamedMesh* pMesh = (*i);
		if(pMesh) {

			pMesh->Release();
			delete pMesh;
		}
	}
	Scene::g_Geometry.clear();

	return true;
}

/**
	Returns the geometry object repsective of the specified name. The function
	fails if either of the parameters are invalid

	@author Dacre Denny
	@param pointer to address of resulting object if it is found
	@param pointer to identification string/object
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::GetGeometry(StreamedMesh **pOut, tCharBuffer *pName) {

	if(!pName || !pOut)
		return false;

	FOREACH(StreamedMesh*, i, Scene::g_Geometry) {

		StreamedMesh* pMesh = (*i);
		if(pMesh) {

			if(strncmp(pName->buffer, pMesh->m_ID.buffer, 32) == 0) {

				*pOut = pMesh;
				return true;
			}

			FOREACH(StreamedMesh::tInstancingProcedural, j, pMesh->m_IPPairs) {

				if(j->m_pInstanceChild) {

					if(strncmp(pName->buffer, j->m_pInstanceChild->m_ID.buffer, 32) == 0) {

						*pOut = j->m_pInstanceChild;
						return true;
					}
				}
			}
		}
	}

	return false;
}

/**
	Enables geometry to be inserted into the scene instance. The function
	fails if the parameter is invalid. If a name conflict is detected the
	specified mesh will replace the existing mesh

	@author Dacre Denny
	@param pointer to the geometry object to be inserted
	@return true if successful and parameter is valid otherwise false
*/
bool Scene::InsertGeometry(StreamedMesh *pIn) {

	if(!pIn)
		return false;

	StreamedMesh* pCached = NULL;
	if(GetGeometry(&pCached, &pIn->m_ID)) {

		if(!Scene::RemoveGeometry(pCached))
			return false;
	}

	Scene::g_Geometry.push_back(pIn);
	return true;
}

/**
	Enables geometry to be removed from the scene instance. The function
	fails if the parameter is invalid. 

	@author Dacre Denny
	@param pointer to the geometry object to be removed
	@return true if successful and parameter is valid otherwise false
*/
bool Scene::RemoveGeometry(StreamedMesh *pIn) {

	if(!pIn)
		return false;

	FOREACH(StreamedMesh*, i, Scene::g_Geometry) {

		StreamedMesh* pMesh = (*i);
		if(pMesh) {

			if(strncmp(pIn->m_ID.buffer, pMesh->m_ID.buffer, 32) == 0) {

				Scene::g_Geometry.erase(i);
				pMesh->Release();
				delete pMesh;
				return true;
			}
		}
	}

	return false;
}

/**
	Enables all objects within the scene instance to be rendered

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool Scene::Draw() {

	FOREACH(StreamedMesh*, i, Scene::g_Geometry) {

		StreamedMesh* pMesh = (*i);
		if(pMesh) {

			pMesh->Draw();
		}
	}

	return true;
}

/**
	Enables all objects within the scene instance to be updated

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool Scene::Update() {

	FOREACH(StreamedMesh*, i, Scene::g_Geometry) {

		StreamedMesh* pMesh = (*i);
		if(pMesh) {

			//pMesh->U();
		}
	}

	return true;
}

/**
	Writes the scene's current state to a file on disk for subsequent recall

	@author Dacre Denny
	@param filename filename to write the data to
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::WriteToDisk(std::string& filename) {

	FILE* pFile = NULL;
	try {

		filename.append(".lvs");
		pFile = fopen(filename.c_str(), "wb");
		if(!pFile)
			throw tException("Failed to open file: %s", filename.c_str());

		//Write scene info

		//Write objects
		fprintf(pFile, "%u", Scene::g_Geometry.size());
		FOREACH(StreamedMesh*, i, Scene::g_Geometry) {

			StreamedMesh* pMesh = (*i);
			
			fwrite(&pMesh->m_ID, sizeof(tCharBuffer), 1, pFile);
			if(!Scene::WriteToDisk(pMesh, std::string(pMesh->m_ID.buffer))){
#pragma message "Log error"
			}
		}

		fclose(pFile);
		return true;

	}catch(tException e) {


		std::string message;
		tException::GetMessage(&e, message);

		if(pFile)
			fclose(pFile);
		return false;
	}
}

/**
	Reads a scene's previous state from the specified file on disk to subsitutue the current for scene state

	@author Dacre Denny
	@param filename filename to read the data from
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::ReadFromDisk(const std::string& filename) {

	FILE* pFile = NULL;
	try {

		std::string filenameFormat = filename + ".lvs";
		pFile = fopen(filenameFormat.c_str(), "rb");
		if(!pFile)
			throw tException("Failed to open file: %s", filenameFormat.c_str());

		//Read scene info

		//Read objects
		uint objectCount = 0;
		fread(&objectCount, sizeof(uint), 1, pFile);
		
		for(uint i = 0; i < objectCount; i++) {

			tCharBuffer objectName;
			fread(&objectName, sizeof(tCharBuffer), 1, pFile);

			StreamedMesh* pObject = NULL;
			if(!Scene::ReadFromDisk(std::string(objectName.buffer), &pObject)) {
#pragma message "Log error"
			}
			else {

				Scene::InsertGeometry(pObject);
			}
		}

		fclose(pFile);
		return true;

	}catch(tException e) {


		std::string message;
		tException::GetMessage(&e, message);

		if(pFile)
			fclose(pFile);
		return false;
	}
}

/**
	Writes a scene object to a file on disk for subsequent recall

	@author Dacre Denny
	@param pMesh pointer to object data source
	@param filename filename to write the object data to
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::WriteToDisk(StreamedMesh* pMesh, std::string& filename) {

	FILE* pFile = NULL;
	try {

		filename.append(".lvo");
		pFile = fopen(filename.c_str(), "wb");
		if(!pFile)
			throw tException("Failed to open file: %s", filename.c_str());
				
		/*
		Write shader related data
		*/
		tCharBuffer shaderFilename;
		ZeroMemory(&shaderFilename, sizeof(tCharBuffer));
		memcpy(&shaderFilename, pMesh->m_Shader->filename.c_str(), min(pMesh->m_Shader->filename.length(), sizeof(tCharBuffer)));
		fwrite(&pMesh->m_Shader->features, sizeof(uint), 1, pFile);
		fwrite(&pMesh->m_Shader->inputFormat, sizeof(uint), 1, pFile);
		fwrite(&shaderFilename, sizeof(tCharBuffer), 1, pFile);
				
		/*
		Write misc data
		*/
		fwrite(&pMesh->m_World, sizeof(D3DXMATRIX), 1, pFile);
		fwrite(&pMesh->m_DeformationScale, sizeof(float), 1, pFile);
		fwrite(&pMesh->m_Deformation, sizeof(bool), 1, pFile);
				
		/*
		Write material data
		*/
		if(pMesh->m_hDecalMaterial) {

			if(!Materials::WriteToDisk(std::string(filename + "_decal"), pMesh->m_hDecalMaterial))
				throw tException("Failed to write material file for: %s", std::string(filename + "_normal").c_str());

			fwrite("1", sizeof(char), 1, pFile);
		}

		if(pMesh->m_hNormalMaterial) {

			if(!Materials::WriteToDisk(std::string(filename + "_normal"), pMesh->m_hNormalMaterial))
				throw tException("Failed to write material file for: %s", std::string(filename + "_normal").c_str());
		}

		fwrite(pMesh->m_hDecalMaterial ? "1" : "0", sizeof(char), 1, pFile);
		fwrite(pMesh->m_hNormalMaterial ? "1" : "0", sizeof(char), 1, pFile);
					
		/*
		Write geometric data
		*/			
		fwrite(&pMesh->m_TriangleCount, sizeof(uint), 1, pFile);
		fwrite(pMesh->m_pTriangleData, sizeof(byte), 
			pMesh->m_TriangleCount * GetVertexFormatSize(pMesh->m_Shader->inputFormat) * 3,
			pFile);

		fclose(pFile);
		return true;

	}catch(tException e) {


		std::string message;
		tException::GetMessage(&e, message);

		if(pFile)
			fclose(pFile);
		return false;
	}
}

/**
	Reads a scene object from a file on disk

	@author Dacre Denny
	@param pMesh pointer to address where the read object data should be stored
	@param filename filename to read the object data from
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::ReadFromDisk(const std::string& filename, StreamedMesh** pOut) {

	FILE* pFile = NULL;
	byte* pTriangleData = NULL;
	StreamedMesh* pObject = NULL;
	Render::tShader* pShader = NULL;

	HMATERIAL hDecal = NULL;
	HMATERIAL hNormal = NULL;

	try {

		if(!pOut)
			throw tException();

		std::string filenameFormat = filename + ".lvs";
		pFile = fopen(filenameFormat.c_str(), "rb");
		if(!pFile)
			throw tException("Failed to open file: %s", filenameFormat.c_str());
				
		/*
		Read shader related data
		*/
		uint features = 0;
		uint format = 0;
		tCharBuffer shaderFilename;
		fread(&features, sizeof(uint), 1, pFile);
		fread(&format, sizeof(uint), 1, pFile);
		fread(&shaderFilename, sizeof(tCharBuffer), 1, pFile);
		if(!Render::Singleton()->CreateShader(std::string(shaderFilename.buffer), format, features, &pShader))
			throw tException("Failed to create shader for: %s", std::string(filename).c_str());
				
		/*
		Read misc data
		*/
		D3DXMATRIX world;
		float deformationScale = 0.0f;
		bool deformation = false;
		fread(&world, sizeof(D3DXMATRIX), 1, pFile);
		fread(&deformationScale, sizeof(float), 1, pFile);
		fread(&deformation, sizeof(bool), 1, pFile);
				
		/*
		Read material data
		*/
		char d = 0,n = 0;
		fread(&d, sizeof(char), 1, pFile);
		fread(&n, sizeof(char), 1, pFile);

		if(d == '1') {
			
			if(!Materials::ReadFromDisk(std::string(filename + "_decal"), &hDecal))
				throw tException("Failed to read material file for: %s", std::string(filename).c_str());
		}

		if(n == '1') {
			
			if(!Materials::ReadFromDisk(std::string(filename + "_normal"), &hNormal))
				throw tException("Failed to read material file for: %s", std::string(filename).c_str());
		}
					
		/*
		Read geometric data
		*/			
		uint triangleCount = 0;
		fread(&triangleCount, sizeof(uint), 1, pFile);

		uint bufferSize = GetVertexFormatSize(format) * triangleCount * 3;
		if(bufferSize == 0)
			throw tException("Invalid buffer size for: %s", std::string(filename).c_str());
		
		pTriangleData = new byte [bufferSize];
		ZeroMemory(pTriangleData, bufferSize);
		fread(pTriangleData, sizeof(byte), bufferSize, pFile);
		fclose(pFile);

		pObject = new StreamedMesh(filename);
		if(!pObject)
			throw tException("Memory error");

		pObject->m_World = world;		
		pObject->m_Deformation = deformation;
		pObject->m_DeformationScale = deformationScale;
		
		pObject->m_hDecalMaterial = hDecal;
		pObject->m_hNormalMaterial = hNormal;
		pObject->m_Shader = pShader;
	
		/**
		Advocates code reuse, creates device buffers, etc
		*/
		HeaderStructs::tGeometryHeader tGeometryHeader = { {0}, format, triangleCount};
		pObject->UpdateGeometry(&tGeometryHeader, pTriangleData);

		Scene::RemoveGeometry(*pOut);
		return true;

	}
	catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);

		if(pObject) {

			Scene::RemoveGeometry(pObject);
		}
		else {

			if(pTriangleData)
				delete [] pTriangleData;
		}

		if(pShader)
			Render::Singleton()->ReleaseShader(pShader);
		if(hDecal)
			Materials::Release(hDecal);
		if(hNormal)
			Materials::Release(hNormal);
		if(pFile)
			fclose(pFile);

		return false;
	}
}